/*
 * gamesessionlist.cpp - Battleship Game plugin
 * Copyright (C) 2014  Aleksey Andreev
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <QTextDocument>

#include "gamesessions.h"
#include "options.h"
#include "invitedialog.h"

#define TIMEOUT_INTERVAL_SECS 60*60*1 // One hour

GameSessionList::GameSessionList(QObject *parent)
	: QObject(parent)
	, stanzaId_(qrand() % 10000)
{
}

GameSessionList::~GameSessionList()
{
//	qDeleteAll(list_); because error: 'virtual GameSession::~GameSession()' is private
	QList<GameSession *>vals = list_.values();
	while(!vals.isEmpty())
		delete vals.takeFirst();
}

GameSessionList *GameSessionList::instance_ = NULL;

GameSessionList *GameSessionList::instance()
{
	if (!instance_)
		instance_ = new GameSessionList();
	return instance_;
}

void GameSessionList::reset()
{
	if (instance_)
	{
		delete instance_;
		instance_ = NULL;
	}
}

GameSession *GameSessionList::createSession(int account, const QString &jid, bool first, const QString &gameId)
{
	if (findGame(account, jid, gameId))
		return NULL;
	GameSession *gs = new GameSession(this, account, jid, first, gameId);
	list_[generateKey(account, jid, gameId)] = gs;
	connect(gs, SIGNAL(sendStanza(int,QString)), this, SIGNAL(sendStanza(int,QString)));
	connect(gs, SIGNAL(doPopup(QString)), this, SIGNAL(doPopup(QString)));
	connect(gs, SIGNAL(playSound(QString)), this, SIGNAL(playSound(QString)));
	connect(gs, SIGNAL(doInviteEvent(int,QString,QString,QObject*,const char*)), this, SIGNAL(doInviteEvent(int,QString,QString,QObject*,const char*)));
	return gs;
}

void GameSessionList::removeGame(GameSession *gs)
{
	list_.remove(list_.key(gs));
	gs->deleteLater();
}

GameSession *GameSessionList::findGame(int account, const QString &jid, const QString &gameId)
{
	QString key = generateKey(account, jid, gameId);
	GameSession *gs = list_.value(key, NULL);
	return gs;
}

GameSession *GameSessionList::findGameByStanzaId(int account, const QString &jid, const QString &stanzaId)
{
	QList<GameSession *>l = list_.values();
	foreach (GameSession *gs, l)
		if (gs->account_ == account && gs->jid_ == jid && gs->stanzaId_ == stanzaId)
			return gs;
	return NULL;
}

bool GameSessionList::processIncomingIqStanza(int account, const QDomElement &xml, const QString &accStatus, bool fromPrivate)
{
	const QString from   = xml.attribute("from");
	const QString iqType = xml.attribute("type");
	if (iqType == "set")
	{
		QDomElement childEl = xml.firstChildElement();
		if (childEl.isNull() || childEl.attribute("xmlns") != "games:board" || childEl.attribute("type") != "battleship")
			return false;

		const QString gameId = childEl.attribute("id");
		if (gameId.isEmpty())
			return true;

		GameSession *gs = NULL;
		const QString tagName = childEl.tagName();
		if (tagName == "create")
		{
			Options *opt = Options::instance();
			if ((!opt->getOption(constDndDisable).toBool() || accStatus != "dnd")
					&& (!fromPrivate || !opt->getOption(constConfDisable).toBool()))
			{
				bool err = true;
				bool first = true;
				const QString firstStr = childEl.attribute("first");
				if (firstStr.toLower() == "true")
				{
					first = false;
					err = false;
				}
				else if (firstStr.toLower() == "false")
					err = false;
				if (!err)
					gs = createSession(account, from, first, gameId);
			}
		}
		else if ((gs = findGame(account, from, gameId)) != NULL)
		{
			if (tagName == "board")
			{
				if (!gs->opBoardChecked_)
				{
					if (gs->stage_ == GameSession::StageInitBoard)
						gs->initOpponentBoard(childEl);
					else if (gs->stage_ == GameSession::StageShowBoard)
					{
						gs->checkOpponentBoard(childEl);
					}
					if (!gs->opBoardChecked_)
						gs->status_ = GameSession::StatusError;
				}
				else
					gs->status_ = GameSession::StatusError;
			}
			else if (tagName == "turn")
			{
				if (gs->stage_ == GameSession::StageShooting && gs->status_ == GameSession::StatusWaitOpponent)
					gs->opponentTurn(childEl);
				else
					gs->status_ = GameSession::StatusError;
			}
		}

		if (gs)
		{
			if (gs->stage_ != GameSession::StageNone)
				gs->sendIqResponse(xml.attribute("id"));
			else
				gs->stanzaId_ = xml.attribute("id");
			gs->executeNextAction();
		}
		else
			sendErrorIq(account, from, xml.attribute("id"));
		return true;
	}
	else
	{
		GameSession *gs = findGameByStanzaId(account, from, xml.attribute("id"));
		if (gs)
		{
			if (iqType == "result")
			{
				bool err = true;
				switch (gs->stage_)
				{
				case GameSession::StageInvitation:
					if (gs->status_ == GameSession::StatusWaitInviteConfirmation)
					{
						gs->status_ = GameSession::StatusNone;
						err = false;
					}
					break;
				case GameSession::StageInitBoard:
				case GameSession::StageShowBoard:
					if (gs->status_ == GameSession::StatusWaitBoardVerification)
					{
						gs->myBoardChecked_ = true;
						gs->status_ = GameSession::StatusNone;
						err = false;
					}
				case GameSession::StageShooting:
					if (gs->status_ == GameSession::StatusWaitShotConfirmation)
					{
						gs->status_ = GameSession::StatusNone;
						err = !gs->handleTurnResult(xml);
					}
				case GameSession::StageEnd:
					err = false;
				default:
					break;
				}
				if (err)
					gs->status_ = GameSession::StatusError;

			}
			else if (gs->stage_ == GameSession::StageInvitation)
			{
				QString msg = tr("From: %1<br />The game was rejected").arg(from);
				QString errMsg = getErrorMessage(xml);
				if (!errMsg.isEmpty())
					msg.append(QString(" (%1)").arg(errMsg));
				doPopup(msg);
				gs->endSession();
				gs = NULL;
			}
			else
				gs->status_ = GameSession::StatusError;
			if (gs)
				gs->executeNextAction();
			return true;
		}
	}
	return false;
}

void GameSessionList::invite(int account, const QString &jid, const QStringList &resList)
{
	GameSession *gs = createSession(account, jid, true, QString());
	if (gs)
		gs->invite(resList);
}

void GameSessionList::sendErrorIq(int account, const QString &jid, const QString &id)
{
	emit sendStanza(account, XML::iqErrorString(jid, id));
}

QString GameSessionList::generateKey(int account, const QString &jid, const QString &gameId)
{
	return QString("%1:%2:%3").arg(QString::number(account)).arg(jid).arg(gameId);
}

QString GameSessionList::getStanzaId(bool bigOffset)
{
	if (bigOffset)
		stanzaId_ += (qrand() % 50) + 5;
	else
		stanzaId_ += (qrand() % 5) + 2;
	return "bsg_" + QString::number(stanzaId_);
}

void GameSessionList::updateGameKey(GameSession *gs)
{
	list_.remove(list_.key(gs));
	list_[generateKey(gs->account_, gs->jid_, gs->gameId_)] = gs;
}

QString GameSessionList::getErrorMessage(const QDomElement &xml)
{
	QDomElement el = xml.firstChildElement("error");
	if (!el.isNull())
	{
		el = el.firstChildElement("error-message");
		if (!el.isNull())
			return el.text();
	}
	return QString();
}

// ---------------- XML --------------------

QString XML::escapeString(const QString &str)
{
	return Qt::escape(str).replace("\"", "&quot;");
}

QString XML::iqErrorString(const QString &jid, const QString &id)
{
	QString stanza = QString("<iq type=\"error\" to=\"%1\" id=\"%2\">\n<error type=\"cancel\" code=\"407\">\n"
			"<error-message>Not Acceptable</error-message>\n</error></iq>\n")
			.arg(XML::escapeString(jid))
			.arg(XML::escapeString(id));
	return stanza;
}

// -------------- GameSession -------------------

GameSession::GameSession(GameSessionList *gsl, int account, const QString &jid, bool first, const QString &gameId)
	: QObject(NULL)
	, gsl_(gsl)
	, stage_(StageNone)
	, status_(StatusNone)
	, account_(account)
	, jid_(jid)
	, first_(first)
	, gameId_(gameId)
	, modifTime_(QDateTime::currentDateTime())
	, inviteDlg_(NULL)
	, boardWid_(NULL)
	, myBoardChecked_(false)
	, opBoardChecked_(false)
	, resign_(false)
{
}

GameSession::~GameSession()
{
	if (!inviteDlg_.isNull())
		inviteDlg_->close();
	if (!boardWid_.isNull())
		boardWid_->close();
}

void GameSession::executeNextAction()
{
	if (stage_ == StageEnd)
		return;

	bool modif = false;
	while (true)
	{
		GameStage old_stage   = stage_;
		GameStatus old_status = status_;
		if (status_ != StatusError)
		{
			switch (stage_)
			{
			case StageNone:
				stage_  = StageInvitation;
				status_ = StatusWaitInviteConfirmation;
				processIncomingInvite();
				break;
			case StageInvitation:
				if (status_ == StatusNone)
				{
					myBoardChecked_ = false;
					opBoardChecked_ = false;
					stage_ = StageInitBoard;
				}
				break;
			case StageInitBoard:
				if (status_ == StatusNone)
				{
					if (myBoardChecked_)
					{
						if (opBoardChecked_)
						{
							stage_ = StageShooting;
							startGame();
						}
					}
					else
					{
						status_ = StatusWaitBoardVerification;
						initBoard();
					}
				}
				break;
			case StageShooting:
				if (status_ == StatusNone)
				{
					if (checkEndGame())
					{
						stage_  = StageShowBoard;
						status_ = StatusNone;
						myBoardChecked_ = false;
						opBoardChecked_ = false;
					}
					else if (!isMyNextTurn())
						status_ = StatusWaitOpponent;
				}
				break;
			case StageShowBoard:
				if (status_ == StatusNone)
				{
					if (myBoardChecked_)
					{
						if (opBoardChecked_)
							stage_ = StageEnd;
					}
					else
					{
						status_ = StatusWaitBoardVerification;
						sendUncoveredBoard();
					}
				}
				break;
			case StageEnd:
				if (status_ == StatusNone)
				{
					checkEndGame();
					if (status_ == StatusNone)
						status_ = StatusError;
				}
				break;
			}
		}
		else if (stage_ != StageEnd)
		{
			setError();
			stage_ = StageEnd;
		}
		if (old_stage == stage_ && old_status == status_)
			break;
		modif = true;
	}
	if (modif)
		modifTime_ = QDateTime::currentDateTime();
	if (inviteDlg_.isNull() && boardWid_.isNull())
	{
		if (stage_ == StageEnd)
			endSession();
		else if (timer_.isNull())
			setTimer();
	}
}

void GameSession::processIncomingInvite()
{
	if (!boardWid_.isNull())
		showInvitationDialog();
	else
		appendInvitationEvent();
}

void GameSession::appendInvitationEvent()
{
	emit doInviteEvent(account_, jid_, tr("%1: Invitation from %2").arg("Battleship Game Plugin").arg(jid_), this, SLOT(showInvitationDialog()));
}

void GameSession::showInvitationDialog()
{
	inviteDlg_ = new InvitationDialog(jid_, first_, boardWid_.data());
	connect(inviteDlg_.data(), SIGNAL(accepted()), this, SLOT(acceptInvitation()));
	connect(inviteDlg_.data(), SIGNAL(rejected()), this, SLOT(rejectInvitation()));
	inviteDlg_->show();
}

void GameSession::acceptInvitation()
{
	status_ = StatusNone;
	sendStanzaResult(stanzaId_);
	executeNextAction();
}

void GameSession::rejectInvitation()
{
	GameSessionList::instance()->sendErrorIq(account_, jid_, stanzaId_);
	endSession();
}

void GameSession::invite(const QStringList &resList)
{
	QWidget *parent = NULL;
	if (!boardWid_.isNull())
		parent = boardWid_.data();
	InviteDialog *dlg = new InviteDialog(jid_.section('/', 0, 0), resList, parent);
	connect(dlg, SIGNAL(acceptGame(QString,bool)), this, SLOT(sendInvite(QString,bool)));
	connect(dlg, SIGNAL(rejected()), this, SLOT(endSession()));
	inviteDlg_ = dlg;
	dlg->show();
}

void GameSession::sendInvite(QString jid, bool first)
{
	first_       = first;
	jid_         = jid;
	modifTime_   = QDateTime::currentDateTime();
	boardStatus_ = QString();
	generateGameId();
	gsl_->updateGameKey(this);

	stage_    = StageInvitation;
	status_   = StatusWaitInviteConfirmation;
	stanzaId_ = gsl_->getStanzaId(true);
	QString stanza = QString("<iq type=\"set\" to=\"%1\" id=\"%2\">"
				"<create xmlns=\"games:board\" id=\"%3\" type=\"battleship\" first=\"%4\" /></iq>\n")
			.arg(XML::escapeString(jid))
			.arg(stanzaId_)
			.arg(XML::escapeString(gameId_))
			.arg((first) ? "true" : "false");
	emit sendStanza(account_, stanza);
}

void GameSession::generateGameId()
{
	gameId_ = "battleship_" + QString::number(qrand(), 16)
			+ QString::number(qrand(), 16) + QString::number(qrand(), 16);
}

void GameSession::endSession()
{
	if (boardWid_.isNull())
		gsl_->removeGame(this);
	else if (stage_ != StageEnd)
	{
		stage_ = StageEnd;
		status_ = StatusError;
	}
}

void GameSession::setError()
{
	status_ = StatusError;
	if (!boardWid_.isNull())
		boardWid_.data()->setError();
}

void GameSession::sendIqResponse(const QString &id)
{
	if (status_ == StatusError)
		gsl_->sendErrorIq(account_, jid_, id);
	else
	{
		QString body;
		if (stage_ == StageShooting && !resign_)
		{
			body = QString("<turn xmlns=\"games:board\" type=\"battleship\" id=\"%1\">\n"
						"<shot result=\"%2\" seed=\"%3\"/>\n</turn>\n")
					.arg(XML::escapeString(gameId_))
					.arg(lastTurnResult_)
					.arg(XML::escapeString(lastTurnSeed_));
		}
		sendStanzaResult(id, body);
	}
}

void GameSession::sendStanzaResult(const QString &id, const QString &body)
{
	QString stanza = QString("<iq type=\"result\" to=\"%1\" id=\"%2\"")
			.arg(XML::escapeString(jid_))
			.arg(XML::escapeString(id));
	if (body.isEmpty())
		stanza.append("/>\n");
	else
		stanza.append(">\n" + body + "</iq>\n");
	emit sendStanza(account_, stanza);
}

void GameSession::initBoard()
{
	if (boardWid_.isNull())
	{
		boardWid_ = new PluginWindow(jid_);
		connect(boardWid_.data(), SIGNAL(gameEvent(QString)), this, SLOT(boardEvent(QString)));
		connect(boardWid_.data(), SIGNAL(destroyed()), this, SLOT(endSession()));
	}
	boardWid_->initBoard();
	boardWid_->show();
}

void GameSession::initOpponentBoard(const QDomElement &xml)
{
	opBoardChecked_ = false;
	bool cells[100];
	int cellsCnt = 0;
	QList<short int>ships;
	for (int i = 0; i < 100; ++i)
		cells[i] = false;
	ships.append(5); ships.append(4); ships.append(3);
	ships.append(2); ships.append(2); ships.append(1); ships.append(1);
	QStringList data("init-opp-board");
	QDomElement el = xml.firstChildElement();
	while (!el.isNull())
	{
		const QString nm = el.nodeName();
		if (nm == "cell")
		{
			int row = el.attribute("row").toInt();
			int col = el.attribute("col").toInt();
			QString hash = el.attribute("hash");
			if (row < 0 || row >= 10 || col < 0 || col >= 10 || hash.length() != 40)
				return;
			int pos = row * 10 + col;
			if (cells[pos])
				return;
			data.append(QString("cell;%1;%2").arg(pos).arg(hash));
			cells[pos] = true;
			++cellsCnt;
		}
		else if (nm == "ship")
		{
			int len = el.attribute("length").toInt();
			QString hash = el.attribute("hash");
			if (!ships.removeOne(len) || hash.length() != 40)
				return;
			data.append(QString("ship;%1;%2").arg(len).arg(hash));
		}
		el = el.nextSiblingElement();
	}
	if (cellsCnt == 100 && ships.empty() && !boardWid_.isNull())
		opBoardChecked_ = (boardWid_->dataExchange(data).first() == "ok");
}

void GameSession::checkOpponentBoard(const QDomElement &xml)
{
	opBoardChecked_ = false;
	bool cells[100];
	int cellsCnt = 0;
	for (int i = 0; i < 100; ++i)
		cells[i] = false;
	QStringList data("check-opp-board");
	QDomElement el = xml.firstChildElement();
	while (!el.isNull())
	{
		if (el.nodeName() == "cell")
		{
			int row = el.attribute("row").toInt();
			int col = el.attribute("col").toInt();
			QString seed = el.attribute("seed");
			if (row < 0 || row >= 10 || col < 0 || col >= 10 || seed.isEmpty())
				return;
			int pos = row * 10 + col;
			if (cells[pos])
				return;
			QString ship = el.attribute("ship").toLower();
			if (ship == "true")
				ship = "1";
			else if (ship != "1")
				ship = "0";
			data.append(QString("%1;%2;%3").arg(pos).arg(ship).arg(seed));
			cells[pos] = true;
			++cellsCnt;
		}
		el = el.nextSiblingElement();
	}
	if (cellsCnt == 100 && !boardWid_.isNull())
		opBoardChecked_ = (boardWid_->dataExchange(data).first() == "ok");
}

void GameSession::opponentTurn(const QDomElement &xml)
{
	bool err   = false;
	int pos    = -1;
	int draw   = 0;
	int resign = 0;
	int accept = 0;
	QDomElement el = xml.firstChildElement();
	while (!el.isNull())
	{
		const QString tagName = el.tagName();
		if (tagName == "shot")
		{
			err = true;
			const QString rowStr = el.attribute("row");
			const QString colStr = el.attribute("col");
			if (rowStr.isEmpty() || colStr.isEmpty() || pos != -1)
				break;
			int row = rowStr.toInt();
			int col = colStr.toInt();
			if (row < 0 || row >= 10 || col < 0 || col >= 10)
				break;
			pos = row * 10 + col;
			err = false;
		}
		else if (tagName == "draw")
			++draw;
		else if (tagName == "accept")
			++accept;
		else if (tagName == "resign")
			++resign;
		el = el.nextSiblingElement();
	}
	if (!err && draw + accept + resign <= 1 && (pos != -1 || resign + accept != 0))
	{
		QStringList data("turn");
		if (draw)
			data.append("draw");
		if (accept)
			data.append("accept");
		if (resign)
			data.append("resign");
		if (pos != -1)
			data.append(QString("shot;%1").arg(pos));
		QStringList res;
		if (!boardWid_.isNull())
			res = boardWid_->dataExchange(data);
		if (res.takeFirst() == "ok")
		{
			while (!res.isEmpty())
			{
				QString s = res.takeFirst();
				QString t = s.section(';', 0, 0);
				if (t == "result")
				{
					lastTurnResult_ = s.section(';', 1, 1);
					lastTurnSeed_   = s.section(';', 2);
				}
				else if (t == "status")
				{
					boardStatus_ = s.section(';', 1);
				}
			}
			status_ = StatusNone;
		}
		else
			status_ = StatusError;
	}
	else
		status_ = StatusError;
}

bool GameSession::handleTurnResult(const QDomElement &xml)
{
	if (boardWid_.isNull())
		return false;

	QStringList data("turn-result");

	QDomElement el = xml.firstChildElement("turn");
	if (!el.isNull())
	{
		if (el.attribute("xmlns") != "games:board"
			|| el.attribute("type") != "battleship" || el.attribute("id") != gameId_)
			return false;

		el = el.firstChildElement("shot");
		if (el.isNull())
			return false;

		QString res  = el.attribute("result");
		if (res != "miss" && res != "hit" && res != "destroy")
			return false;

		QString seed = el.attribute("seed");
		data.append(QString("shot-result;%1;%2").arg(res).arg(seed));
	}
	QStringList res = boardWid_->dataExchange(data);
	QString s = res.takeFirst();
	if (s == "ok")
	{
		while (!res.isEmpty())
		{
			s = res.takeFirst();
			if (s.section(';', 0, 0) == "status")
			{
				boardStatus_ = s.section(';', 1);
				break;
			}
		}
		return true;
	}
	return false;
}

void GameSession::boardEvent(QString data)
{
	QStringList dataList = data.split('\n');
	QString dataStr = dataList.takeFirst();
	QString body;
	if (dataStr == "covered-board")
	{
		body = QString("<board xmlns=\"games:board\" type=\"battleship\" id=\"%1\">\n")
				.arg(gameId_);
		while (!dataList.isEmpty())
		{
			dataStr = dataList.takeFirst();
			QString str = dataStr.section(';', 0, 0);
			if (str == "cell")
			{
				int pos = dataStr.section(';', 1, 1).toInt();
				int row = pos / 10;
				int col = pos % 10;
				QString hash = dataStr.section(';', 2);
				body.append(QString("<cell row=\"%1\" col=\"%2\" hash=\"%3\"/>\n")
							.arg(row)
							.arg(col)
							.arg(hash));
			}
			else if (str == "ship")
			{
				int len = dataStr.section(';', 1, 1).toInt();
				QString hash = dataStr.section(';', 2);
				body.append(QString("<ship length=\"%1\" hash=\"%2\"/>\n")
							.arg(len)
							.arg(hash));
			}
		}
		body.append("</board>\n");
	}
	else if (dataStr == "turn")
	{
		int  pos    = -1;
		bool draw   = false;
		bool accept = false;
		bool resign = false;
		while (!dataList.isEmpty())
		{
			dataStr = dataList.takeFirst();
			QString str = dataStr.section(';', 0, 0);
			if (str == "pos")
				pos = dataStr.section(';', 1).toInt();
			else if (str == "draw")
				draw = true;
			else if (str == "accept")
				accept = true;
			else if (str == "resign")
				resign = true;
		}
		body = QString("<turn xmlns=\"games:board\" type=\"battleship\" id=\"%1\">\n")
				.arg(XML::escapeString(gameId_));
		if (pos != -1)
		{
			int row = pos / 10;
			int col = pos % 10;
			body.append(QString("<shot row=\"%1\" col=\"%2\"/>\n").arg(row).arg(col));
		}
		if (draw)
			body.append("<draw/>\n");
		if (accept)
			body.append("<accept/>\n");
		if (resign)
			body.append("<resign/>\n");
		body.append("</turn>\n");
		status_ = StatusWaitShotConfirmation;
		if (accept || resign)
			boardStatus_ = "end";
	}
	else if (dataStr == "new-game")
	{
		invite(QStringList(jid_.section('/', 1)));
		return;
	}
	stanzaId_ = gsl_->getStanzaId(false);
	QString stanza = QString("<iq type=\"set\" to=\"%1\" id=\"%2\">\n")
			.arg(XML::escapeString(jid_))
			.arg(stanzaId_);
	stanza.append(body);
	stanza.append("</iq>\n");
	emit sendStanza(account_, stanza);
}

void GameSession::startGame()
{
	if (!boardWid_.isNull())
	{
		QStringList data("start");
		if (first_)
			data.append("first");
		QStringList res = boardWid_->dataExchange(data);
		if (res.takeFirst() == "ok")
		{
			while (!res.isEmpty())
			{
				QString s = res.takeFirst();
				if (s.section(';', 0, 0) == "status")
				{
					boardStatus_ = s.section(';', 1);
					break;
				}
			}
		}
		else
			boardStatus_ == QString();
	}
}

bool GameSession::checkEndGame()
{
	return (boardStatus_ == "end");
}

bool GameSession::isMyNextTurn()
{
	return (boardStatus_ == "turn");
}

void GameSession::sendUncoveredBoard()
{
	if (boardWid_.isNull())
		return;
	QStringList res = boardWid_->dataExchange(QStringList("get-uncovered-board"));
	QString body;
	while (!res.isEmpty())
	{
		QString dataStr = res.takeFirst();
		int pos = dataStr.section(';', 0, 0).toInt();
		int row = pos / 10;
		int col = pos % 10;
		QString ship = dataStr.section(';', 1, 1);
		QString seed = dataStr.section(';', 2);
		body.append(QString("<cell row=\"%1\" col=\"%2\" ship=\"%3\" seed=\"%4\"/>\n")
					.arg(row)
					.arg(col)
					.arg(ship)
					.arg(XML::escapeString(seed)));
	}
	stanzaId_ = gsl_->getStanzaId(false);
	QString stanza = QString("<iq type=\"set\" to=\"%1\" id=\"%2\">\n")
			.arg(XML::escapeString(jid_))
			.arg(stanzaId_);
	stanza.append(QString("<board xmlns=\"games:board\" type=\"battleship\" id=\"%1\">\n")
				.arg(XML::escapeString(gameId_)));
	stanza.append(body);
	stanza.append("</board>\n</iq>\n");
	emit sendStanza(account_, stanza);
}

void GameSession::setTimer()
{
	timer_ = new QTimer(this);
	timer_->setSingleShot(true);
	connect(timer_.data(), SIGNAL(timeout()), this, SLOT(timeout()));
	timer_->setInterval(TIMEOUT_INTERVAL_SECS*1000);
}

void GameSession::timeout()
{
	QDateTime currTime = QDateTime::currentDateTime();
	if (inviteDlg_.isNull() && boardWid_.isNull())
	{
		if (modifTime_.secsTo(currTime) >= TIMEOUT_INTERVAL_SECS)
			endSession();
	}
	else
		delete timer_;
}
